package services

import (
    "cing.io/cing/internal/conductor"
    "context"
    "io"
    "net/http"

    "github.com/gin-gonic/gin"
    log "github.com/sirupsen/logrus"
    "google.golang.org/protobuf/encoding/protojson"

    "cing.io/cing-api/planner"
    "cing.io/cing-api/task"
    "cing.io/cing/internal/apiserver/common"
    "cing.io/cing/internal/clientcache"
    "cing.io/cing/internal/storage"
)

var TaskService = &taskService{}

type taskService struct{}

func (*taskService) GetTask(ctx *gin.Context) {
    namespace, name := ctx.Param("namespace"), ctx.Param("name")

    log.Debugf("namespace: %s, name: %s", namespace, name)

    if namespace == "" {
        ctx.JSON(http.StatusBadRequest, common.NeedNamespace)
    } else if name == "" {
        tasks := storage.ListTask(namespace)
        ctx.JSON(http.StatusOK, tasks)
    } else {
        tsk := storage.GetTask(namespace, name)
        ctx.JSON(http.StatusOK, tsk)
    }
}

func (*taskService) CreateTask(ctx *gin.Context) {
    tsk := &task.Task{}
    bytes, _ := io.ReadAll(ctx.Request.Body)
    err := protojson.Unmarshal(bytes, tsk)
    if err != nil {
        log.Error(err)
    }
    log.Debug("Create task", tsk.String())

    // get planner cli
    taskType := tsk.TaskType
    plannerCli := clientcache.Planner.GetPlanner(taskType)
    if plannerCli == nil {
        ctx.JSON(http.StatusBadRequest, common.TaskTypeNotSupport)
        return
    }

    // store task
    err = storage.PutTask(tsk)
    if err != nil {
        ctx.JSON(http.StatusBadRequest, err.Error())
        return
    }

    resp, err := plannerCli.OnTaskCreate(context.TODO(), &planner.OnTaskCreateRequest{Task: tsk})
    if err != nil {
        _ = storage.DeleteTask(tsk.Namespace, tsk.Name)
        ctx.JSON(http.StatusBadRequest, common.CreateTaskError)
        return
    }

    if !resp.TaskSpecCheckResult.Ok {
        ctx.JSON(http.StatusBadRequest, resp.TaskSpecCheckResult.ErrorMsg)
        return
    }

    if resp.NextPlan != nil {
        err := conductor.PutPlan(resp.NextPlan)
        if err != nil {
            ctx.JSON(http.StatusBadRequest, common.CreateTaskError)
            return
        }
    }

    ctx.JSON(http.StatusOK, gin.H{
        "task":     tsk,
        "nextPlan": resp.NextPlan,
    })
}

func (*taskService) UpdateTask(ctx *gin.Context) {
    tsk := &task.Task{}
    bytes, _ := io.ReadAll(ctx.Request.Body)
    err := protojson.Unmarshal(bytes, tsk)
    if err != nil {
        log.Error(err)
    }
    log.Info(tsk.String())

    // get planner cli
    taskType := tsk.TaskType
    plannerCli := clientcache.Planner.GetPlanner(taskType)
    if plannerCli == nil {
        ctx.JSON(http.StatusBadRequest, common.TaskTypeNotSupport)
        return
    }

    resp, err := plannerCli.OnTaskUpdate(context.TODO(), &planner.OnTaskUpdateRequest{Task: tsk})
    if err != nil {
        ctx.JSON(http.StatusBadRequest, common.UpdateTaskError)
        return
    }

    if resp.TaskSpecCheckResult != nil && !resp.TaskSpecCheckResult.Ok {
        ctx.JSON(http.StatusBadRequest, resp.TaskSpecCheckResult.ErrorMsg)
        return
    }

    var errors []error

    if resp.CancelPlan != nil {
        taskFullName := resp.CancelPlan.TaskFullName
        planSelector := resp.CancelPlan.PlanSelector
        if planSelector != nil {
            switch planSelector.(type) {
            case *planner.CancelPlan_SelectAtTime:
                selectAtTime := planSelector.(*planner.CancelPlan_SelectAtTime)
                time := selectAtTime.SelectAtTime.StartTime.AsTime().UnixMilli()
                err := storage.DeletePlan(taskFullName, time)
                if err != nil {
                    errors = append(errors, err)
                }

            case *planner.CancelPlan_SelectLastPlan:
                err := storage.DeleteLastPlan(taskFullName)
                if err != nil {
                    errors = append(errors, err)
                }
            }
        }
    }

    if resp.NextPlan != nil {
        err := conductor.PutPlan(resp.NextPlan)
        if err != nil {
            ctx.JSON(http.StatusBadRequest, common.CreateTaskError)
            return
        }
    }

    ctx.JSON(http.StatusOK, gin.H{
        "task":     tsk,
        "nextPlan": resp.NextPlan,
        "errors":   errors,
    })
}

func (*taskService) DeleteTask(ctx *gin.Context) {
    namespace, name := ctx.Param("namespace"), ctx.Param("name")

    log.Debugf("namespace: %s, name: %s", namespace, name)

    if namespace == "" {
        ctx.JSON(http.StatusBadRequest, common.NeedNamespace)
        return
    } else if name == "" {
        ctx.JSON(http.StatusBadRequest, common.NeedName)
        return
    }
    err := storage.DeleteTask(namespace, name)
    if err != nil {
        ctx.JSON(http.StatusBadRequest, err.Error())
    } else {
        ctx.Status(http.StatusOK)
    }
}
